--                        -*- Mode: lua; lua-indent-level: 2; tab-width: 2 -*-
local function UnitTest()
	local time = time
	local test_number = 0
	local test_name = "Test"
	local function next_test(arg)
		test_number = test_number + 1
		test_name = "Test "..test_number
		if arg then
			test_name = test_name..": "..arg
		end
		--print("Starting "..test_name)
	end

	-- Test: Get Library
	next_test("Setup")
	local LibSignal = LibStub:GetLibrary("LibSignal-1.0")
	assert(LibSignal,test_name..": Library Failed to Load")

	local self = {}

	local signals = nil
	local signal_log = {}
	local signals_matched = 0

	local function DumpArgs(...)
		local res = {}
		for i = 1,select('#',...) do
			local arg = select(i,...)
			local type = type(arg)
			if arg == self then
				arg = "self"
			elseif type == "string" then
				arg = string.format("%q",arg)
			else
				arg = tostring(arg)
			end
			table.insert(res,arg)
		end
		return table.concat(res,", ")
	end

	local function LogSignal(...)
		table.insert(signal_log,{ signal = LibSignal.signal, root = LibSignal.signal.root, args = { ... } })
		--[[
		local msg = table.getn(signal_log)..": Caught<"..tostring(LibSignal.signal)..">: ("..DumpArgs(...)..")"
		if DEFAULT_CHAT_FRAME then
			DEFAULT_CHAT_FRAME:AddMessage(msg)
		else
			print(msg)
		end
		]]
	end

	local function AssertSignal(test, name, root, ...)
		local available = table.getn(signal_log)
		signals_matched = signals_matched + 1
		if signals_matched > available then
			assert(false,test..": Failed did not receive signal, expected signal ("..tostring(name)..")")
			signals_matched = table.getn(signal_log)
			return
		end
		local record = signal_log[signals_matched]
		assert(name == record.signal or name == record.signal.name,
					 test..": Failed got signal: ("..tostring(record.signal)..") expected: ("..tostring(name)..")")
		assert(root == record.root or root == record.root.name,
					 test..": Failed got signal root: ("..tostring(record.root)..") expected: ("..tostring(root)..")")
		local args_failure = nil
		if select('#',...) ~= table.maxn(record.args) then
			args_failure = "different number of arguments"
		else
			for i = 1,select('#',...) do
				if select(i,...) ~= record.args[i] then
					args_failure = i.."th argument does not match"
					break
				end
			end
		end
		if args_failure then assert(false,test..": "..args_failure..", got: ("..DumpArgs(unpack(record.args))..") expected: ("..DumpArgs(...)..")") end
	end

	local function AssertAllMatched(test)
		local available = table.getn(signal_log)
		if (available < signals_matched) then assert(false,test..": too few signals received") end
		while (available > signals_matched) do
			signals_matched = signals_matched + 1
			local record = signal_log[signals_matched]
			assert(false,test..": unexpected <"..tostring(record.signal)..">: ("..DumpArgs(unpack(record.args))..")")
		end
	end

	-- Test: Check for unregistered signal
	next_test("SignalHasListeners")
	assert(LibSignal.SignalHasListeners("MySignal") == nil,test_name..": False postive for registered signal")

	-- Test: Register for LibSignal
	next_test("RegisterSignalListener0arg")
	---- Since this is the first listener to be registered for LibSignal it will cause
	---- LibSignal:StartListening to be raised
	LibSignal.RegisterSignalListener("LibSignal",self,LogSignal) -- Listen for all LibSignal signals
	AssertSignal(test_name, "LibSignal:StartListening:LibSignal", "LibSignal", LibSignal.GetSignal("LibSignal"))
	AssertAllMatched(test_name)

	-- Test: Get Common Signals
	next_test("GetSignal")
	local LibSignalSignal = LibSignal.GetSignal("LibSignal")
	assert(LibSignalSignal ~= nil,test_name..": Failed to lookup signal: LibSignal")
	local MySigSignal = LibSignal.GetSignal("MySig")
	assert(MySigSignal ~= nil,test_name..": Failed to lookup signal: MySig")
	local MySigASignal = LibSignal.GetSignal("MySig:A")
	assert(MySigASignal ~= nil,test_name..": Failed to lookup signal: MySig:A")
	AssertAllMatched(test_name)

	-- Test: Register MySig with 4 initial arguments
	next_test("RegisterSignalListener4arg")
	---- Again this should fire LibSignal:StartListening
	LibSignal.RegisterSignalListener("MySig",self,LogSignal,'a"c',nil,"b",false)
	AssertSignal(test_name, "LibSignal:StartListening:MySig", "LibSignal", LibSignal.GetSignal("MySig"))
	AssertAllMatched(test_name)

	-- Test: Register MySig again 1 argument
	next_test("RegisterSignalListener1arg")
	---- Nothing received this time since MySig is already listened for
	LibSignal.RegisterSignalListener("MySig",nil,LogSignal,"c")
	AssertAllMatched(test_name)

	-- Test: Unregister the second MySig
	next_test("UnregisterSignalListener")
	---- Since we used a nil handle the handle LogSignal was used
	---- Nothing received this time since MySig still has one listener
	LibSignal.UnregisterSignalListener("MySig",LogSignal)
	AssertAllMatched(test_name)

	-- Test: Fire MySig
	next_test("FireSignal")
	LibSignal.FireSignal("MySig","test7")
	AssertSignal(test_name,"MySig","MySig",'a"c',nil,"b",false,"test7")
	AssertAllMatched(test_name)

	-- Test: Fire MySig:A:B:C
	next_test("FireSignal Nested")
	LibSignal.FireSignal("MySig:A:B:C","test8")
	AssertSignal(test_name,"MySig:A:B:C","MySig",'a"c',nil,"b",false,"test8")
	AssertAllMatched(test_name)

	-- Test: Fire A:MySig:B
	next_test("FireSignal Other")
	LibSignal.FireSignal("A:MySig:B","e")
	AssertAllMatched(test_name)

	-- Test: Check signals listened for by self
	next_test("GetSignalsForHandle")
	signals = LibSignal.GetSignalsForHandle(self)
	
	assert(signals[LibSignalSignal],test_name..": Did not return LibSignal from GetSignalsForHandle")
	assert(signals[MySigSignal],test_name..": Did not return MySig from GetSignalsForHandle")
	signals[LibSignalSignal] = nil
	signals[MySigSignal] = nil
	assert(next(signals) == nil,test_name..": Returned additional signals")
	AssertAllMatched(test_name)

	-- Test: Unregister last MySig listener
	next_test("UnregisterSignalListener")
	LibSignal.UnregisterSignalListener("MySig",self)
	AssertSignal(test_name, "LibSignal:StopListening:MySig", "LibSignal", LibSignal.GetSignal("MySig"))
	AssertAllMatched(test_name)

	-- Test: Check signals listened for by self
	next_test("GetSignalsForHandle")
	signals = LibSignal.GetSignalsForHandle(self)
	assert(signals[LibSignalSignal],test_name..": Did not return LibSignal from GetSignalsForHandle")
	assert(not signals[MySigSignal],test_name..": Did return MySig from GetSignalsForHandle")
	signals[LibSignalSignal] = nil
	signals[MySigSignal] = nil
	assert(next(signals) == nil,test_name..": Returned additional signals")
	AssertAllMatched(test_name)

	-- Test: Fire a signal that is no longer listened for
	next_test("FireSignal No Listeners")
	LibSignal.FireSignal("MySig:A:B:C",test_name)
	AssertAllMatched(test_name)

	-- Test: Reregister MySig using 1 argument special case
	next_test("RegisterSignalListenerSelf")
	LibSignal.RegisterSignalListener("MySig",self,LogSignal,self)
	AssertSignal(test_name, "LibSignal:StartListening:MySig", "LibSignal", LibSignal.GetSignal("MySig"))
	AssertAllMatched(test_name)

	-- Test: Fire the Signal
	next_test("FireSignal Self")
	LibSignal.FireSignal("MySig:A:B:C",test_name)
	AssertSignal(test_name,"MySig:A:B:C","MySig",self,test_name)
	AssertAllMatched(test_name)

	-- Test: Register MySig under "ahandle"
	next_test("RegisterSignalListenerAHandle")
	LibSignal.RegisterSignalListener("MySig","ahandle",LogSignal,"a",nil,"b")
	AssertAllMatched(test_name)

	-- Test: Register MySig:A under "ahandle"
	next_test("RegisterSignalListenerAHandle2")
	LibSignal.RegisterSignalListener("MySig:A","ahandle",LogSignal)
	AssertSignal(test_name, "LibSignal:StartListening:MySig", "LibSignal", LibSignal.GetSignal("MySig:A"))
	AssertAllMatched(test_name)

	-- Test: GetRegisteredSignalsForHandle
	next_test("GetRegisteredSignalsForRoot")
	signals = LibSignal.GetRegisteredSignalsForRoot("MySig")
	assert(signals[MySigSignal],test_name..": Did not return MySig from GetRegisteredSignalsForRoot")
	assert(signals[MySigASignal],test_name..": Did not return MySig:A from GetRegisteredSignalsForRoot")
	signals[MySigSignal] = nil
	signals[MySigASignal] = nil
	assert(next(signals) == nil,test_name..": Returned additional signals")
	AssertAllMatched(test_name)
	

	-- Test: Unregister self's MySig listener
	next_test("UnregisterSignalListenerSelf")
	LibSignal.UnregisterSignalListener("MySig",self)
	AssertAllMatched(test_name)

	-- Test: Unregister "ahandle"
	next_test("UnregisterSignalHandleAHandle")
	LibSignal.UnregisterSignalHandle("ahandle")
	-- Either order could happen here...
	if (table.getn(signal_log) > signals_matched
			and rawget(signal_log[signals_matched+1].args,1) == LibSignal.GetSignal("MySig")) then
		AssertSignal(test_name, "LibSignal:StopListening:MySig", "LibSignal", LibSignal.GetSignal("MySig"))
		AssertSignal(test_name, "LibSignal:StopListening:MySig", "LibSignal", LibSignal.GetSignal("MySig:A"))
	else
		AssertSignal(test_name, "LibSignal:StopListening:MySig", "LibSignal", LibSignal.GetSignal("MySig:A"))
		AssertSignal(test_name, "LibSignal:StopListening:MySig", "LibSignal", LibSignal.GetSignal("MySig"))
	end
	AssertAllMatched(test_name)

	-- Test: Create and Use anonymous signal
	next_test("GetAnonymousSignal")
	local sig = LibSignal.GetAnonymousSignal()
	AssertAllMatched(test_name)
	sig:RegisterListener("bhandle",LogSignal)
	AssertSignal(test_name, "LibSignal:StartListening", "LibSignal", sig)
	AssertAllMatched(test_name)
	sig:Fire("a","b","c")
	AssertSignal(test_name, sig, sig, "a", "b", "c")
	AssertAllMatched(test_name)
	sig(1,2)
	AssertSignal(test_name, sig, sig, 1, 2)
	AssertAllMatched(test_name)
	sig:UnregisterListener("bhandle")
	AssertSignal(test_name, "LibSignal:StopListening", "LibSignal", sig)
	AssertAllMatched(test_name)

	-- Test: Create a one time listener
	next_test("OneTimeListener")
	AssertAllMatched(test_name)
	sig:RegisterListener("onehandle",function (...) LogSignal(...); return true; end)
	AssertSignal(test_name, "LibSignal:StartListening", "LibSignal", sig)
	AssertAllMatched(test_name)
	sig:Fire("a")
	AssertSignal(test_name, sig, sig, "a")
	AssertSignal(test_name, "LibSignal:StopListening", "LibSignal", sig)
	AssertAllMatched(test_name)
	sig:Fire("b")
	AssertAllMatched(test_name)
	
	-- Test: Fire 10 million signals
	local rep = profiler and 1e6 or 1e7
	local maxdur = 3e-6 * rep
	-- Set duration up a bit for testing when system is under load
	next_test("Fire"..rep.."Signals")
	local hit = 0
	local hit2 = 0
	local function hitfunc() hit = hit + 1 end
	local function hitfunc2() hit2 = hit2 + 1 end
	local ManyHits = LibSignal.GetSignal("ManyHits")
	ManyHits:RegisterListener(hitfunc,hitfunc)
	ManyHits:RegisterListener(hitfunc2,hitfunc2)
	AssertSignal(test_name, "LibSignal:StartListening:ManyHits", "LibSignal", ManyHits)
	local mem, mem1, mem2, now, duration
	AssertAllMatched(test_name)
	do
		collectgarbage("collect")
		collectgarbage("stop")
		mem = collectgarbage("count")
		now = time()
		-- Do in groups of 10 to reduce the weight of the for loop
		-- in profiling.
		for i = 1,rep,10 do
			ManyHits()
			ManyHits()
			ManyHits()
			ManyHits()
			ManyHits()
			ManyHits()
			ManyHits()
			ManyHits()
			ManyHits()
			ManyHits()
		end
		mem1 = collectgarbage("count")
		collectgarbage("collect") --include the cost of garbage in time
		mem2 = collectgarbage("count")
		duration = time() - now
		collectgarbage("restart")
	end
	assert(hit == rep, test_name..": Hit signal "..hit.." times, expected: "..rep)
	assert(hit2 == rep, test_name..": Hit signal "..hit2.." times, expected: "..rep)
	-- Handle case where mem2 < mem
	local maxmem = math.max(mem,mem2)
	local minmem = math.min(mem,mem2)
	if not profiler then
		print(test_name..": Duration per call: "..(duration / rep))
		assert(duration <= maxdur,test_name..": Took to long to send signals")
	end
	if (mem2 ~= mem) then
		print(test_name..": Lost memory during test: "..(mem-mem2))
	end
	assert(mem2 == minmem,test_name..": Leaked memory during test: "..(mem2-minmem))
	assert(mem1 <= maxmem,test_name..": Generated garbage during test: "..(mem1-maxmem))
	AssertAllMatched(test_name)

	do
		local msg = "Passed all test for LibSignal-1.0"
		if DEFAULT_CHAT_FRAME then
			DEFAULT_CHAT_FRAME:AddMessage(msg)
		else
			print(msg)
		end
	end
end
LibStub:GetLibrary("LibSignal-1.0").UnitTest = UnitTest
